<template>
  <div>
    <modal-view
      ref="modalViewRef"
      title="图片裁切"
      width="800px"
      submit-text="确定"
      :before-submit="beforeSubmit"
      @close="onClose"
    >
      <div class="page-body">
        <div class="page-main">
          <div class="page-img" ref="imgBox">
            <div
              class="page-clip-box"
              ref="clipBox"
              :style="{
                'width': clipData.clipBoxWpx + 'px', 'height': clipData.clipBoxHpx + 'px',
                'top': clipData.clipBoxTop + 'px', 'left': clipData.clipBoxLeft + 'px',
              }"
            >
              <!-- 这个是用于拖动的 -->
              <div class="page-clip-content"></div>

              <!-- 上边框 -->
              <div
                class="page-clip-box-line"
                style="left:0px;top:-5px;cursor:s-resize;width:100%"
                @mousedown="onClipAddLineResize('top', $event)"
              ></div>
              <!-- 下边框 -->
              <div
                class="page-clip-box-line"
                style="left:0px;bottom:-5px;cursor:s-resize;width:100%"
                @mousedown="onClipAddLineResize('bottom', $event)"
              ></div>
              <!-- 左边框 -->
              <div
                class="page-clip-box-line"
                style="left:-5px;top:0px;cursor:e-resize;height:100%"
                @mousedown="onClipAddLineResize('left', $event)"
              ></div>
              <!-- 右边框 -->
              <div
                class="page-clip-box-line"
                style="right:-5px;top:0px;cursor:e-resize;height:100%"
                @mousedown="onClipAddLineResize('right', $event)"
              ></div>
              <!-- 左上角 -->
              <div
                class="page-clip-box-block"
                style="left:-5px;top:-5px;cursor:nw-resize"
                @mousedown="onClipBlockMouseDown('rb', $event)"
              ></div>
              <!-- 左下角 -->
              <div
                class="page-clip-box-block"
                style="left:-5px;bottom:-5px;cursor:sw-resize"
                @mousedown="onClipBlockMouseDown('rt', $event)"
              ></div>
              <!-- 右上角 -->
              <div
                class="page-clip-box-block"
                style="right:-5px;top:-5px;cursor:ne-resize"
                @mousedown="onClipBlockMouseDown('lb', $event)"
              ></div>
              <!-- 右下角 -->
              <div
                class="page-clip-box-block"
                style="right:-5px;bottom:-5px;cursor:se-resize"
                @mousedown="onClipBlockMouseDown('lt', $event)"
              ></div>
            </div>
          </div>
        </div>
        <!-- 右侧数据 -->
        <div class="page-right">
          <div class="page-right-title">图片要求宽高比:</div>
          <div class="page-right-wh">宽({{ _this.clipBoxW }}):高({{ _this.clipBoxH }})</div>
          <div class="page-right-line">预览框</div>
          <div>
            <div
              class="page-img-show"
              :style="{
                'width': imgShowData.imgShowWidth + 'px',
                'height': imgShowData.imgShowHeight + 'px',
              }"
            >
              <img
                :src="_this.imgUrl"
                :style="{
                  'width': imgShowData.imgShowInWidth + 'px',
                  'transform': `translate(${imgShowData.imgShowInLeft}px,${imgShowData.imgShowInTop}px)`
                }"
              />
            </div>

            <!-- <button @click="imgShowToCanvas">生成canvas</button> -->

            <div id="test-asd"></div>
          </div>
        </div>
      </div>
    </modal-view>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: "ImageCutting",
})
</script>

<script lang="ts" setup>
import { ref, reactive, nextTick } from "vue";
import ModalView from "../ModalView";
import { ElMessage } from "element-plus";

let emit = defineEmits<{
  (e: "close"): void;
  (e: "submit", value: File): void
}>()

defineExpose({
  show
})

/**
 * 弹框
 */
let modalViewRef = ref<any>();

/**
 * 裁切框
 */
let clipBox = ref<HTMLElement>();

/**
 * 用于放置需要裁切图片的盒子
 */
let imgBox = ref<HTMLElement>();

/**
 * 预览框
 */
let imgShowCanvas = ref<HTMLElement>();


let _this = reactive({
  /**
   * 切图框的宽度比例
   */
  clipBoxW: 0,
  /**
   * 切图框的高度比例
   */
  clipBoxH: 0,
  /**
   * 图片链接
   */
  imgUrl: "",

  /**
   * 图片的实际宽度
   */
  imgWidth: 0,

  /**
   * 图片的实际高度
   */
  imgHeight: 0
})

/**
 * 打开页面
 */
function show(imgUrl: string, clipBoxW: number, clipBoxH: number) {
  _this.imgUrl = imgUrl;
  _this.clipBoxW = clipBoxW;
  _this.clipBoxH = clipBoxH;
  modalViewRef.value.show();
  nextTick(() => {
    init();
  });
}

/**
 * 页面关闭
 */
function onClose() {
  emit("close");
}


/**
 * 页面保存
 */
function beforeSubmit(): Promise<void> {
  return new Promise((resolve, reject) => {
    //生成Blob
    imgShowToCanvas()
      .then(e => {
        emit("submit", e)
        resolve();
      })
      .catch(() => {
        ElMessage.warning("裁切图片生成错误,请重试");
        reject();
      });
  })

}


/**
 * 切割功能加载
 */
function init() {
  //首先需要加载图片
  let imgDom = document.createElement("img");
  imgDom.src = _this.imgUrl;
  imgDom.style.width = "100%";
  imgDom.style.height = "100%";
  imgDom.style.display = "block";
  //将图片添加到页面中
  imgBox.value?.appendChild(imgDom);
  //图片加载完成
  imgDom.onload = e => {
    _this.imgWidth = imgDom.offsetWidth;
    _this.imgHeight = imgDom.offsetHeight;
    //根据图片计算裁切框的实际尺寸和位置
    resetClipBox();
    //计算展示框中 背景图片的长度
    resetImgShowInWidth();
    //计算展示框中背景图片的位置
    resetImgShowInResize();
  };

  //为切割框添加拖动效果
  clipAddDrag();

  //创建一个预览框
  createImgShow();
}



/**
 * 鼠标拖拽委托
 */
function mouseDragEvent(
  /**
   * 需要委托的dom元素
   */
  eventDom: HTMLElement,
  {
    /**
     *鼠标按下去
     */
    onDown = (e: MouseEvent) => { },
    /**
     * 当鼠标移动的时候
     */
    onMove = (e: MouseEvent) => { },
    /**
     * 当鼠标放开的时候
     */
    onUp = (e: MouseEvent) => { }
  }
) {
  /**
   * 当鼠标按下去的时候
   */
  eventDom.onmousedown = function (e) {
    onDown(e);
    document.onmousemove = function (e) {
      onMove(e);
    };
    document.onmouseup = function (e) {
      onUp(e);
      //置空鼠标拖动  , 和鼠标抬起的事件
      document.onmousemove = null;
      document.onmouseup = null;
    };
  };
}

/**
 * ================================
 *  切割框功能
 * ================================
 */
let clipData = reactive({
  /**
   * 切图框的实际宽度
   */
  clipBoxWpx: 0,

  /**
   * 切图框的实际高度
   */
  clipBoxHpx: 0,

  /**
   * 切图框的实际top
   */
  clipBoxTop: 0,

  /**
   * 切图框的实际left
   */
  clipBoxLeft: 0,
})


/**
 * 根据图片计算裁切框的实际尺寸和位置
 */
function resetClipBox() {
  //计算出实际的裁切框的长宽来
  let k = _this.imgWidth / _this.imgHeight - _this.clipBoxW / _this.clipBoxH;
  if (k > 0) {
    clipData.clipBoxHpx = _this.imgHeight;
    clipData.clipBoxWpx = (_this.imgHeight / _this.clipBoxH) * _this.clipBoxW;
  } else if (k < 0) {
    clipData.clipBoxWpx = _this.imgWidth;
    clipData.clipBoxHpx = (_this.imgWidth / _this.clipBoxW) * _this.clipBoxH;
  } else {
    clipData.clipBoxWpx = _this.imgWidth;
    clipData.clipBoxHpx = _this.imgHeight;
  }
  //计算位置 , 应该在实际图片的中间
  clipData.clipBoxLeft = (_this.imgWidth - clipData.clipBoxWpx) / 2;
  clipData.clipBoxTop = (_this.imgHeight - clipData.clipBoxHpx) / 2;
}


/**
 * 为切割框添加拖动效果
 */
function clipAddDrag() {
  if (!clipBox.value) return;
  //为切割框添加拖动效果
  //添加拖拽效果
  let clipBoxElement = clipBox.value;
  let clipDrag = clipBoxElement.querySelector(".page-clip-content");

  if (clipDrag instanceof HTMLElement) {
    //偏移量
    let disX: number = 0;
    let disY: number = 0;
    //为切割框添加拖动
    mouseDragEvent(clipDrag, {
      onDown(e: any) {
        // 鼠标按下，计算当前元素距离可视区的距离
        //这里面需要计算外边距的偏移量;
        disX = e.clientX - clipBoxElement.offsetLeft;
        disY = e.clientY - clipBoxElement.offsetTop;
      },
      onMove: e => {
        let top = e.clientY - disY;
        let left = e.clientX - disX;
        //添加边界判断
        if (top <= 0) {
          clipData.clipBoxTop = 0;
        } else if (top + clipData.clipBoxHpx <= _this.imgHeight) {
          clipData.clipBoxTop = top;
        } else {
          clipData.clipBoxTop = _this.imgHeight - clipData.clipBoxHpx;
        }
        if (left <= 0) {
          clipData.clipBoxLeft = 0;
        } else if (left + clipData.clipBoxWpx <= _this.imgWidth) {
          clipData.clipBoxLeft = left;
        } else {
          clipData.clipBoxLeft = _this.imgWidth - clipData.clipBoxWpx;
        }
      },
      onUp: () => {
        //计算展示框中背景图片的位置
        resetImgShowInResize();
      }
    });
  }
}

/**
 * 为切割框添加边框 调整功能
 */
function onClipAddLineResize(
  position: "top" | "bottom" | "left" | "right",
  e: MouseEvent
) {
  // 添加边框调整功能
  // console.log(clipLineList);

  let cacheTop: number = clipData.clipBoxTop;
  let cacheLeft: number = clipData.clipBoxLeft;
  let cacheWidth: number = clipData.clipBoxWpx;
  let cacheHeight: number = clipData.clipBoxHpx;

  //记录下鼠标当前的位置
  let disY: number = e.clientY;
  let disX: number = e.clientX;

  /**
   * 固定角
   */
  let fixed: "rt" | "lt" | "lb" | "rb" = "lt";
  if (position == "bottom" || position == "right") {
    fixed = "lt";
  } else if (position == "top") {
    fixed = "lb";
  } else if (position == "left") {
    fixed = "rt";
  }

  /**
   * 缩放的依据
   */
  let positionWH: "width" | "height" = "width";
  if (position == "top" || position == "bottom") {
    positionWH = "height";
  } else {
    positionWH = "width";
  }

  document.onmousemove = e => {
    /**
     *  缩放距离
     */
    let count: number = 0;
    switch (position) {
      case "top": {
        count = -(e.clientY - disY);
        break;
      }
      case "bottom": {
        count = e.clientY - disY;
        break;
      }
      case "left": {
        count = -(e.clientX - disX);
        break;
      }
      case "right": {
        count = e.clientX - disX;
        break;
      }
    }

    //进行等比缩放
    resetSize(fixed, positionWH, count, {
      cacheLeft,
      cacheTop,
      cacheHeight,
      cacheWidth
    });
    // 获取鼠标当前偏移量
    // disY = e.clientY;
  };
  document.onmouseup = e => {
    document.onmousemove = null;
    document.onmouseup = null;
    //计算展示框中背景图片宽度
    resetImgShowInWidth();
    //计算展示框中背景图片的位置
    resetImgShowInResize();
  };
}


/**
 * 当点击切割框角的时候
 */
function onClipBlockMouseDown(fixed: "lt" | "lb" | "rt" | "rb", e: MouseEvent) {
  let cacheTop: number = clipData.clipBoxTop;
  let cacheLeft: number = clipData.clipBoxLeft;
  let cacheWidth: number = clipData.clipBoxWpx;
  let cacheHeight: number = clipData.clipBoxHpx;

  //处理操作顶部边框 和 底部边框
  //记录下鼠标当前的位置
  let disY: number = e.clientY;
  let disX: number = e.clientX;
  document.onmousemove = e => {
    /**
     * 缩放距离
     */
    let count = 0;
    /**
     * 缩放的依据
     */
    let positionWH: "width" | "height" = "width";

    let x = 0;
    let y = 0;

    switch (fixed) {
      //固定左上角
      case "lt": {
        x = e.clientX - disX;
        y = e.clientY - disY;
        break;
      }
      //固定左下角
      case "lb": {
        x = e.clientX - disX;
        y = -(e.clientY - disY);
        break;
      }
      //固定右上角
      case "rt": {
        x = -(e.clientX - disX);
        y = e.clientY - disY;
        break;
      }
      //固定右下
      case "rb": {
        x = -(e.clientX - disX);
        y = -(e.clientY - disY);
        break;
      }
    }

    if (Math.abs(x / _this.clipBoxW) > Math.abs(y / _this.clipBoxH)) {
      count = x;
      positionWH = "width";
    } else {
      count = y;
      positionWH = "height";
    }

    // if (fixed != "lt") {
    //   count = -count;
    // }
    // console.log(x, y);
    // console.log(position);

    //进行等比缩放
    resetSize(fixed, positionWH, count, {
      cacheLeft,
      cacheTop,
      cacheHeight,
      cacheWidth
    });
  };

  document.onmouseup = e => {
    document.onmousemove = null;
    document.onmouseup = null;
    //计算展示框中背景图片宽度
    resetImgShowInWidth();
    //计算展示框中背景图片的位置
    resetImgShowInResize();
  };
}



/**
 * 等比缩放
 */
function resetSize(
  // 固定角
  fixed: "lt" | "lb" | "rt" | "rb",
  // fixed: "top" | "bottom" | "left" | "right",
  //缩放对应的 宽 -高(表示 缩放数量对应的是宽 还是i高)
  positionWH: "width" | "height",
  //缩放数量
  cout: number,
  //这些数据 是表示 赋值一个用于计算的默认值
  //此时的cout 就不是根据实际位置计算 , 而是根据赋予的 默认位置计算
  { cacheTop = 0, cacheLeft = 0, cacheWidth = 0, cacheHeight = 0 } = {}
) {
  let top: number = cacheTop || clipData.clipBoxTop;
  let left: number = cacheLeft || clipData.clipBoxLeft;
  let width: number = cacheWidth || clipData.clipBoxWpx;
  let height: number = cacheHeight || clipData.clipBoxHpx;

  // 更新宽高
  if (positionWH == "width") {
    //缩放为宽
    width += cout;
    height += (cout * _this.clipBoxH) / _this.clipBoxW;
  } else {
    //缩放为宽
    height += cout;
    width += (cout * _this.clipBoxW) / _this.clipBoxH;
  }

  // 更新位置
  switch (fixed) {
    //固定左上角
    case "lt": {
      break;
    }
    //固定左下角
    case "lb": {
      if (positionWH == "width") {
        top = cacheTop - (height - cacheHeight);
      } else {
        top -= cout;
      }
      break;
    }
    //固定右上角
    case "rt": {
      if (positionWH == "width") {
        left -= cout;
      } else {
        left = cacheLeft - (width - cacheWidth);
      }
      break;
    }
    //固定右下角
    case "rb": {
      if (positionWH == "width") {
        top = cacheTop - (height - cacheHeight);
        left -= cout;
      } else {
        left = cacheLeft - (width - cacheWidth);
        top -= cout;
      }
      break;
    }
  }

  //添加边界判断
  if (top < 0) return;
  if (left < 0) return;
  if (left + width > _this.imgWidth) return;
  if (top + height > _this.imgHeight) return;
  //赋值
  clipData.clipBoxTop = top;
  clipData.clipBoxLeft = left;
  clipData.clipBoxWpx = width;
  clipData.clipBoxHpx = height;
}

/**
 * ================================
 *  预览框数据
 * ================================
 */

let imgShowData = reactive({
  /**
   * 预览框的宽度
   */
  imgShowWidth: 250,

  /**
   * 预览框的高度
   */
  imgShowHeight: 0,

  /**
   * 预览框中背景图片应有的长度
   */
  imgShowInWidth: 0,

  /**
   * 预览框中背景图片的top
   */
  imgShowInTop: 0,

  /**
   * 预览框中背景图片的left
   */
  imgShowInLeft: 0,
})

/**
 * 创建预览框canvas
 */
function createImgShow() {
  imgShowData.imgShowHeight = (imgShowData.imgShowWidth / _this.clipBoxW) * _this.clipBoxH;
}

/**
 * 计算展示框中 背景图片应有的长度
 */
function resetImgShowInWidth() {
  imgShowData.imgShowInWidth = (imgShowData.imgShowWidth * _this.imgWidth) / clipData.clipBoxWpx;
}

/**
 * 计算展示框中 背景图片的位置
 */
function resetImgShowInResize() {
  //计算比例
  let a = clipData.clipBoxWpx / imgShowData.imgShowWidth;
  imgShowData.imgShowInTop = -(clipData.clipBoxTop / a);
  imgShowData.imgShowInLeft = -(clipData.clipBoxLeft / a);
}



/**
 * 将图片转化为canvas
 */
function imgShowToCanvas(): Promise<File> {
  let canvas = document.createElement("canvas");
  //为了保证图片清晰度没有问题 , 我们需要等比放大
  let k = 2;
  canvas.width = imgShowData.imgShowWidth * k;
  canvas.height = imgShowData.imgShowHeight * k;
  let ctx = canvas.getContext("2d");
  let img = document.createElement("img");
  img.src = _this.imgUrl;
  return new Promise((resolve, reject) => {
    img.onload = () => {
      if (ctx instanceof CanvasRenderingContext2D) {
        ctx.drawImage(
          img,
          imgShowData.imgShowInLeft * k,
          imgShowData.imgShowInTop * k,
          imgShowData.imgShowInWidth * k,
          ((imgShowData.imgShowInWidth * _this.imgHeight) / _this.imgWidth) * k
        );
        //canvas 转化为base64
        canvas.toBlob(e => {
          if (e instanceof Blob) {
            resolve(new File([e], "1.png"));
          } else {
            reject(e);
          }
        });
      }
    };
  });
}
</script>

<style lang="less" scoped>
.page-body {
  display: flex;
}
.page-main {
  flex-shrink: 0;
  user-select: none;
  @gzColor: rgb(231, 231, 231);
  width: 500px;
  height: 500px;
  // border: 1px solid rgba(0, 0, 0, 0.1);
  background-image: linear-gradient(
      45deg,
      @gzColor 25%,
      transparent 25%,
      transparent 75%,
      @gzColor 75%,
      @gzColor
    ),
    linear-gradient(
      45deg,
      @gzColor 26%,
      transparent 26%,
      transparent 74%,
      @gzColor 74%,
      @gzColor
    );
  background-size: 30px 30px;
  background-position: 0 0, 15px 15px;
  display: flex;
  justify-content: center;
  align-items: center;
}
.page-img {
  max-width: 100%;
  max-height: 100%;
  position: relative;
  overflow: hidden;
  > img {
    width: 100%;
    height: 100%;
    display: block;
  }
}

.page-clip-box {
  position: absolute;
  // box-sizing: content-box;
  // user-select: 500 red;
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0 0 500px 500px rgba(0, 0, 0, 0.5);
}
// 用于拖动
.page-clip-content {
  width: 100%;
  height: 100%;
  cursor: move;
}
// 角块
.page-clip-box-block {
  width: 9px;
  height: 9px;
  border: 1px solid #e3e4e5;
  background-color: rgba(0, 0, 0, 0.3);
  position: absolute;
  // z-index: -1;
}
// 边框
.page-clip-box-line {
  position: absolute;
  width: 9px;
  height: 9px;
  // background-color: red;
}

.page-right {
  flex-basis: 0;
  flex-grow: 1;
  margin-left: 20px;
}
.page-right-title {
  font-size: 15px;
  margin-bottom: 5px;
}
.page-right-wh {
  font-size: 18px;
  font-weight: bold;
}
.page-right-line {
  margin: 15px 0 5px 0;
  font-size: 13px;
}
.page-img-show {
  border: 1px solid rgba(0, 0, 0, 0.1);
  overflow: hidden;
}
</style>

